﻿// ==UserScript==
// @name			Leonerdo
// @match			http://www.facebook.com/*
// @match			https://www.facebook.com/*
// @version			1.0.0
// @author			Adam Zydron
// @description		Userscript for Facebook Chat - adding common whiteboard
// @run-at			document-end
// @grant			none
// ==/UserScript==

//*****************************************************************************************************


// Funkcje bariery skrypt <-> kod strony
function addScript(s){
	var body = document.getElementsByTagName("body");
	var script = document.createElement('script');
	script.innerHTML = s;
	body[0].appendChild(script);
}
function addStyle(s){
	var head = document.getElementsByTagName("head");
	var styleTag = document.createElement('style');
	styleTag.type = 'text/css';
	styleTag.innerHTML = s;
	head[0].appendChild(styleTag);
}
function obj2str(x){
	function getSource(x){
		if (typeof x == 'number') return ''+x;
		if (typeof x == 'string') return '"'+x+'"';
		if (typeof x == 'function') return ''+x;
		if (typeof x == 'object'){
			var res = '{';
			for (y in x) res += y + ':' + getSource(x[y]) + ',';
			return res + '}';
		}
		console.log("obj2str error: " + (typeof x));
	}

	var result="";
	if (x == null) return "null";
	if (x == undefined) return "undefined";
	if (x.toSource) return x.toSource();
	return getSource(x);
};
addScript(""+addScript);
addScript(""+addStyle);
addScript(""+obj2str);


//*****************************************************************************************************
//*****************************************************************************************************
//*****************************************************************************************************
// REALIZATOR


// Funkcja wysyłająca wiadomość do użytkowanika o podanym ID
function sendMessageToUser(userID, msg_txt){
	var msg = require('MercuryMessages').constructUserGeneratedMessageObject(msg_txt, "source:chat:web", "user:"+userID);
	msg.message_id = require('MercuryMessages').generateNewClientMessageID(msg.timestamp);
	msg.status = 0; msg.specific_to_list = [msg.author, 'fbid:'+userID];
	msg.client_thread_id = msg.thread_id; msg.thread_id = null; msg.forward_message_ids = [];
	require('MercuryServerRequests')._sendNewMessageToServer(msg, "mercury");
}
addScript(""+sendMessageToUser);


// Funkcje obsługujące handshake
addScript("window.LEONERDO_HANDSHAKE_1 = 14285700;");
addScript("window.LEONERDO_HANDSHAKE_2 = 14285701;");
addScript("window.LEONERDO_HANDSHAKE_TODO = {};");
function initHandshake(userID){
	new (require('AsyncRequest'))().setData({typ:LEONERDO_HANDSHAKE_1,to:userID}).setURI('/ajax/messaging/typ.php').send();
}
function sendHandshakeAccept(userID){
	new (require('AsyncRequest'))().setData({typ:LEONERDO_HANDSHAKE_2,to:userID}).setURI('/ajax/messaging/typ.php').send();
}
function commitHandshake(userID){
	var todo_list = LEONERDO_HANDSHAKE_TODO[userID];
	LEONERDO_HANDSHAKE_TODO[userID] = [];
	for(var t=0; t<todo_list.length; ++t) (todo_list[t])();
}
function subscribeHandshake(userID, f){
	if (!LEONERDO_HANDSHAKE_TODO[userID]) LEONERDO_HANDSHAKE_TODO[userID] = [];
	LEONERDO_HANDSHAKE_TODO[userID].push(f);
}

addScript(""+initHandshake);
addScript(""+sendHandshakeAccept);
addScript(""+commitHandshake);
addScript(""+subscribeHandshake);

// Funkcja wpinająca Handshake i przechwytywanie przychodzących metawiadomości
function patch__me(mname, src){
	mod = require(mname); if (!mod) return;
	if (mod.__patched) return; mod.__patched = true;
	if (typeof mod == 'object'){	
		if (mname == "ChannelManager"){
			new_processTransportData = function(x, y){
				meta_occur = false;
				if (y.json.t == "msg") for(var i=0; i<y.json.ms.length; ++i){

					// Obsługa Handshake'u
					if (y.json.ms[i].type == "typ"){
						if (y.json.ms[i].st == LEONERDO_HANDSHAKE_1) sendHandshakeAccept(y.json.ms[i].from);
						if (y.json.ms[i].st == LEONERDO_HANDSHAKE_2) commitHandshake(y.json.ms[i].from);
					}

					// Obsługa metawiadomości
					else if ((y.json.ms[i].type == "messaging") && (y.json.ms[i].event == "deliver") && (y.json.ms[i].message.body.indexOf("LEONERDO_META:")>-1)){
						meta_occur = true;
						
						// Informowanie serwera o przeczytaniu wiadomości
						X={ids:{}}; X.ids[y.json.ms[i].message.tid]=true;
						require('MercuryServerDispatcher').trySend('/ajax/mercury/change_read_status.php', X);
						require('MercuryMessages').deleteMessages("msg", [y.json.ms[i].message.threading_id]);
						
						// Uciszanie wiadomości
						y.json.ms[i].message.mercury_author_id = "";
						y.json.ms[i].message.is_unread = false;
						y.json.ms[i].unread_counts.inbox = 0;
						y.json.ms[i].unread_counts.sent = 0;
						y.json.ms[i].is_unread = false;
					}
					else if ((y.json.ms[i].type == "msg") && (y.json.ms[i].msg) && (y.json.ms[i].msg.text.indexOf("LEONERDO_META:")>-1)){

						// Parsowanie wiadomości i przekazywanie jej do silnika
						msg = y.json.ms[i].msg.text; ID = y.json.ms[i].id;
						parsed_msg = JSON.parse(msg.substr(msg.indexOf("{")));
						Leonerdo.receiveMessage(ID, parsed_msg, y.json.ms[i].from_name);
					}
				}

				if (meta_occur) for(var i=0; i<y.json.ms.length; ++i) if (y.json.ms[i].unread) y.json.ms[i].unread = y.json.ms[i].unseen = 0;
				new_processTransportData.old.apply(this, arguments);
			}
			new_processTransportData.old = mod._processTransportData;
			mod._processTransportData = new_processTransportData;
		}
	}
}
addScript(""+patch__me);


// Funnkcja pomocnicza przy wpinaniu innych funkcji
function patch__d(){
	try{
		if (!window.__d) return; if (window.__d_orig) return;

		__d_orig = __d;
		__d = function(module, req_list, factory){
			if ((module == "ChannelManager") || (module == "MercuryServerRequests")){
				var fact_src = ""+factory;
				fact_src = fact_src.substr(0, fact_src.length-1) + "patch__me('"+module+"'); }";
				new_factory = new Function("","return ("+fact_src+");")();
				arguments[2]=new_factory;
			}
			return __d_orig.apply(this, arguments);
		}
		patch__me("ChannelManager");
		patch__me("MercuryServerRequests");
	} catch (e) { console.log(e); }
}
addScript(""+patch__d);
function tryToPath__d(e){ if ((e.target.tagName == "SCRIPT") && (e.target.src.indexOf(".js")>-1)) addScript("patch__d();"); }
if (document.body) addScript("patch__d();"); else window.addEventListener("DOMContentLoaded", tryToPath__d, false);
document.addEventListener( 'DOMNodeInserted', tryToPath__d, false );
document.addEventListener( 'DOMNodeInsertedIntoDocument', tryToPath__d, false );


// Tworzenie stylu przycisku włączającego wspólną tablicę
var ResourcesURL = 'http://static.ak.fbcdn.net/rsrc.php/';
addStyle(".fbc_cmd{cursor:pointer;background-position:0px -1468px;}.sp_afub6d{background-image:url("+ResourcesURL+"v2/ys/x/iDzpojNRL9i.png);background-repeat:no-repeat;display:inline-block;height:16px;width:16px}");


// Wstawianie przycisku włączającego wspólną tablicę
function tryToAddLeonerdoButton(){
	try{
		var titleBarList = document.getElementsByClassName('mls titlebarButtonWrapper rfloat');
		if (titleBarList.length == 0) return;
		for (var i=0; i<titleBarList.length; i=i+1){
			var titleBar = titleBarList[i];	
			var menuElem = titleBar.getElementsByClassName('itemAnchor');
			var userID = null;
			for (var j=0; j<menuElem.length; ++j) if ((menuElem[j].href) && (menuElem[j].href.indexOf('/messages/')>=0)){
				menuElem[j].href.match(/\/messages\/(\S*)/);
				userID = RegExp.$1;
			}
			
			var name = titleBar.parentNode.getElementsByClassName('titlebarText')[0].text;
			
			if (titleBar.getElementsByClassName('sp_afub6d fbc_cmd').length>0) continue;
			var btn = document.createElement('a');
			btn.setAttribute('href', '#');
			btn.setAttribute('onclick', 'Leonerdo.openObject('+userID+', "'+name+'");return false;');
			btn.setAttribute('class', 'sp_afub6d fbc_cmd');
			btn.setAttribute('style', 'margin-top:5px;');
			btn.setAttribute('title', 'Otw\u00f3rz wsp\u00f3ln\u0105 tablic\u0119 Leonerdo');
			btn.setAttribute('tooltip-alignh', 'center');
			btn.setAttribute('data-hover', 'tooltip');
			titleBar.insertBefore(btn, titleBar.lastChild);
		}
	} catch (e){}
}
if (document.body) tryToAddLeonerdoButton(); else window.addEventListener("DOMContentLoaded", tryToAddLeonerdoButton, false);
document.addEventListener( 'DOMNodeInserted', tryToAddLeonerdoButton, false );
document.addEventListener( 'DOMNodeInsertedIntoDocument', tryToAddLeonerdoButton, false );



//*****************************************************************************************************
//*****************************************************************************************************
//*****************************************************************************************************
// SILNIK


var Leonerdo = {

	/*** PRIVATE ***/

		// Obiekty Leonerdo
		_objects: {},

		// Wewnętrzne powiązania pomiędzy obiektami
		_intraObjects: {},

		// Funkcja tworząca odbiornik rysujący dane na ekranie
		_createPaintReceiver: function(ID, name){
			function generateReceiverBody(name){
				var bodyScript = [
					function $(id){return document.getElementById(id);},
					function refreshPositions(){
						var w=window.innerWidth||document.body.clientWidth;
						var h=window.innerHeight||document.body.clientHeight;
						p_width = parseInt($("paletteMenu").style.width.substr(0, $("paletteMenu").style.width.length-2));
						$("board").style.width = (w - p_width) + "px";
						$("palette").style.height = (h-4) + "px";
					},
					function startup(_ID){

						// Ustawienia środowiskowe
						this.ID = _ID;
						Leonerdo = window.opener.Leonerdo;
						ParentObject = Leonerdo._objects[ID];
						for(var i=0; i<ParentObject.content.length; ++i) pushObject(ParentObject.content[i]);

						// Ustawienia graficzne
						refreshPositions();
						setWidth(2);
						setType("pen");
						colorButtons = document.getElementsByClassName("color_button");
						for(var i=0; i<colorButtons.length; ++i) colorButtons[i].setAttribute("onmousedown", "setColor(event);return false;");
						this.firstColor = "black"; this.secondColor = "white";
						refreshColors();
						
						// Ustawienia zdarzeń
						document.mouseIsDown = false;
						document.mouseIsOver = false;

						
						document.mouseUp = function(t, e){
							document.mouseIsDown = document.mouseIsOver = false; 
							commitTmp();
						}
						document.mouseMove = function(t, e){
							if (!document.mouseIsOver) return;
							moveTmp.apply(t, dispatchXY(t, e));
						}
						document.mouseDown = function(t, e){
							if (e.button == 0){
								document.mouseIsDown = document.mouseIsOver = true;
								createTmp.apply(t, dispatchXY(t, e));
							} else if (e.button == 2){
								if (document.mouseIsDown){
									document.mouseIsDown = document.mouseIsOver = false;
									discardTmp();
								} else {
									s = document.getElementById("svg_board"); T = e.target;
									if (s.isEqualNode(T.parentNode)){
										params = {ID:T.getAttribute("class")};
										var L = Leonerdo.getIntraObjects(ID);
										for(var j=0; j<L.length; ++j) L[j].remove(params);
										ParentObject.remove(params);
									}
								}
							}
						}
						document.mouseOut = function (t, e){
							s = document.getElementById("svg_board"); T = e.relatedTarget;
							if ((!e.relatedTarget) || ((!s.isEqualNode(T.parentNode)) && (!s.isEqualNode(T)))) document.mouseIsOver = false;
						}
						document.mouseOver = function(t, e){
							if (document.mouseIsOver) return; document.mouseIsOver = true; 
							escapeTmp.apply(t, dispatchXY(t,e));
						}
						
						document.onmouseup = function(e){ if (!document.mouseIsDown) return false; document.mouseUp(this, e); return false; }
						$("board").setAttribute("onmousedown", "mouseDown(this, event); return false;");
						$("board").setAttribute("onmousemove", "if (!document.mouseIsDown) return false; mouseMove(this, event); return false;");
						$("board").setAttribute("onmouseover", "if (!document.mouseIsDown) return false; mouseOver(this, event); return false;");
						$("board").setAttribute("onmouseout", "if (!document.mouseIsDown) return false; mouseOut(this, event); return false;");
					},
					function dispatchXY(t, e){ return [e.pageX - t.offsetLeft, e.pageY - t.offsetTop]; },
					function setPathMode(){
						discardTmp = function(){
							var L = $("svg_board").childNodes; path = [];
							for(var i=0; i<L.length; ++i) if (L[i].getAttribute("class")=="TMP") path.push(L[i]);
							for(var i=0; i<path.length; ++i) path[i].parentNode.removeChild(path[i]);
						}
						createTmp = function(x,y){
							var path = ParentObject.graphObj.path("TMP", "", firstColor, currentWidth);
							path.addPoint(x,y);
							$("svg_board").appendChild(path);
						}
						commitTmp = function(){
							var L = $("svg_board").childNodes; path = [];
							for(var i=0; i<L.length; ++i) if (L[i].getAttribute("class")=="TMP") path.push(L[i]);
							for(var i=0; i<path.length; ++i){
								params = {
									ID: "PATH_"+Math.floor((Math.random()*10000000)+10000000),
									points: path[i].pointsList,
									color: path[i].color,
									width: path[i].width,
								}
								path[i].parentNode.removeChild(path[i]);
								var L = Leonerdo.getIntraObjects(ID);
								for(var j=0; j<L.length; ++j) L[j].path(params);
								ParentObject.path(params);
							}
						}
						escapeTmp = function(x,y){ createTmp(x,y); }
						moveTmp = function(x,y){
							var path;
							L = $("svg_board").childNodes;
							for(var li=L.length-1; li>=0; --li) if (L[li].getAttribute("class")=="TMP"){ path=L[li]; break; }
							path.addPoint(x,y);
						}
					},
					function setRectMode(){
						discardTmp = function(){
							var L = $("svg_board").childNodes; path = [];
							for(var i=0; i<L.length; ++i) if (L[i].getAttribute("class")=="TMP") path.push(L[i]);
							for(var i=0; i<path.length; ++i) path[i].parentNode.removeChild(path[i]);
						}
						createTmp = function(x,y){
							var rect = ParentObject.graphObj.rect("TMP", x, y, 0, 0, secondColor, firstColor, currentWidth);
							$("svg_board").appendChild(rect);
						}
						commitTmp = function(){
							var L = $("svg_board").childNodes; rect = [];
							for(var i=0; i<L.length; ++i) if (L[i].getAttribute("class")=="TMP") rect.push(L[i]);
							for(var i=0; i<rect.length; ++i){
								params = {
									ID: "RECT_"+Math.floor((Math.random()*10000000)+10000000),
									color: rect[i].color,
									stroke_color: rect[i].stroke_color,
									stroke_width: rect[i].stroke_width,
									width: rect[i].getAttribute("width"),
									height: rect[i].getAttribute("height"),
									x: rect[i].getAttribute("x"),
									y: rect[i].getAttribute("y"),
								}
								rect[i].parentNode.removeChild(rect[i]);
								var L = Leonerdo.getIntraObjects(ID);
								for(var j=0; j<L.length; ++j) L[j].rect(params);
								ParentObject.rect(params);
							}
						}
						escapeTmp = function(x,y){}
						moveTmp = function(x,y){
							var rect;
							L = $("svg_board").childNodes;
							for(var li=L.length-1; li>=0; --li) if (L[li].getAttribute("class")=="TMP"){ rect=L[li]; break; }
							rect.setCoords(x,y);
						}
					},
					function setEllipseMode(){
						discardTmp = function(){
							var L = $("svg_board").childNodes; path = [];
							for(var i=0; i<L.length; ++i) if (L[i].getAttribute("class")=="TMP") path.push(L[i]);
							for(var i=0; i<path.length; ++i) path[i].parentNode.removeChild(path[i]);
						}
						createTmp = function(x,y){
							var ellipse = ParentObject.graphObj.ellipse("TMP", x, y, 0, 0, secondColor, firstColor, currentWidth);
							$("svg_board").appendChild(ellipse);
						}
						commitTmp = function(){
							var L = $("svg_board").childNodes; ellipse = [];
							for(var i=0; i<L.length; ++i) if (L[i].getAttribute("class")=="TMP") ellipse.push(L[i]);
							for(var i=0; i<ellipse.length; ++i){
								params = {
									ID: "ELLIPSE_"+Math.floor((Math.random()*10000000)+10000000),
									color: ellipse[i].color,
									stroke_color: ellipse[i].stroke_color,
									stroke_width: ellipse[i].stroke_width,
									cx: ellipse[i].getAttribute("cx"),
									cy: ellipse[i].getAttribute("cy"),
									rx: ellipse[i].getAttribute("rx"),
									ry: ellipse[i].getAttribute("ry"),
								}
								ellipse[i].parentNode.removeChild(ellipse[i]);
								var L = Leonerdo.getIntraObjects(ID);
								for(var j=0; j<L.length; ++j) L[j].ellipse(params);
								ParentObject.ellipse(params);
							}
						}
						escapeTmp = function(x,y){}
						moveTmp = function(x,y){
							var ellipse;
							L = $("svg_board").childNodes;
							for(var li=L.length-1; li>=0; --li) if (L[li].getAttribute("class")=="TMP"){ ellipse=L[li]; break; }
							ellipse.setCoords(x,y);
						}
					},
					function clearBoard(){
						var L = Leonerdo.getIntraObjects(ID);
						for(var i=0; i<L.length; ++i) L[i].clear();
						ParentObject.clear();
					},
					function getCurrentColor(){ return currentColorButton.getAttribute("color"); },
					function refreshColors(){
						var first = document.getElementById("firstColorField");
						var second = document.getElementById("secondColorField");
						first.style.fill = firstColor;
						second.style.fill = secondColor;
					},
					function setColor(e){
						if (e.button == 0) firstColor = e.target.style.backgroundColor; 
							else secondColor = e.target.style.backgroundColor;
						refreshColors();
					},
					function setType(type){
						var L = document.getElementsByClassName("typeField");
						for(var i=0; i<L.length; ++i){
							L[i].style.stroke = "rgb(150,150,150)";
							L[i].style.fill = "rgb(200,200,200)";
						}
						if (type == "pen"){
							document.getElementById("penTypeField").style.stroke = "black";
							setPathMode();
						}
						if (type == "rect"){
							document.getElementById("rectTypeField").style.stroke = "black";
							setRectMode();
						}
						if (type == "ellipse"){
							document.getElementById("ellipseTypeField").style.stroke = "black";
							setEllipseMode();
						}
					},
					function setWidth(width){
						var L = document.getElementsByClassName("widthField");
						for(var i=0; i<L.length; ++i){
							L[i].style.stroke = "rgb(150,150,150)";
							L[i].style.fill = "rgb(200,200,200)";
						}
						this.currentWidth = width;
						document.getElementById("width"+width+"Field").style.stroke = "black";
					},
					function pushObject(obj){ $("svg_board").appendChild(obj); },
					function removeObject(ID){
						L = []; 
						for(var i=0; i<$("svg_board").childNodes.length; ++i) 
							if ($("svg_board").childNodes[i].getAttribute("class") == ID)
								L.push($("svg_board").childNodes[i]);
						for(var i=0; i<L.length; ++i) L[i].parentNode.removeChild(L[i]);
					},
					function clear(){
						var SL = [];
						for(var si=0; si<$("svg_board").childNodes.length; ++si)
							if ($("svg_board").childNodes[si].getAttribute("class")!="TMP") 
								SL.push($("svg_board").childNodes[si]);
						for(var si=0; si<SL.length; ++si) SL[si].parentNode.removeChild(SL[si]);
					},
				];

				// BACK-END
				var head="<HEAD>\n";
				head+="<title>"+name+" - Leonerdo Whiteboard</title>\n";
				head+="<style type='text/css'>\n";
				head+="   .color_button{ width:23px; height:23px; margin-left:1; margin-top:1; border:0px outset; }\n";
				head+="   .type_button{ width:100%; height:23px; margin-left:1; margin-top:1; border:2px outset; }\n";
				head+="   .type_button_up{ width:100%; height:23px; margin-left:1; margin-top:1; border:2px outset; }\n";
				head+="   .type_button_down{ width:100%; height:23px; margin-left:1; margin-top:1; border:2px inset; }\n";
				head+="</style>\n";
				head+="<script>\n";
				for(var i=0; i<bodyScript.length; ++i){
					var fun = obj2str(bodyScript[i]);
					if (fun.charAt(0)=='(') fun=fun.substr(1, fun.length-2);
					head=head+fun+"\n";
				}
				head+="</script>\n";
				head+="</HEAD>\n";


				// FRONT-END
				var body="<BODY onresize='refreshPositions();' oncontextmenu='return false;' style='margin-left:0; margin-bottom:0; margin-right:0; margin-top:0; overflow-x:hidden;overflow-y:hidden;'>\n";
				body+="<div id='paletteMenu' style='width:100px; height:100%; float:left;'>\n";
				body+="    <div id='palette' style='width:96px; height:100%; float:left; background-color:#AAAAAA; border: 2px black solid;'>\n";
				body+="        <input type='button' value='Clear board' onclick='clearBoard();' style='width:100%;'/>\n";
				body+="        <div id='separator' style='width:100%; height:1px; float:top; '></div>\n";
				body+="        <div id='separator' style='width:100%; height:2px; float:top; background-color:#000000;'></div>\n";
				body+="        <div class='type_bar' style='width:100%; height:57px; float:top;'>\n";
				body+="            <div id='separator' style='width:100%; height:5px; float:top; '></div>\n";
				body+="            <div onmousedown='return false;'><center><font size=2><b>TYPE</b></font></center></div>\n";
				body+="            <svg id='type_board' xmlns='http://www.w3.org/2000/svg' version='1.1' width='100%' height='100%'>\n";
				body+="                <rect id='penTypeField' class='typeField' onmousedown='setType(\"pen\")' x='5' y='5' width='25' height='25'/>\n";
				body+="                <rect id='rectTypeField' class='typeField' onmousedown='setType(\"rect\")' x='35' y='5' width='25' height='25'/>\n";
				body+="                <rect id='ellipseTypeField' class='typeField' onmousedown='setType(\"ellipse\")' x='65' y='5' width='25' height='25'/>\n";
				body+="                <polyline onmousedown='setType(\"pen\")' points='8,12 27,23' style='stroke:black;stroke-width:2;'/>\n";
				body+="                <rect onmousedown='setType(\"rect\")' x='38' y='12' width='19' height='10' style='fill:yellow;stroke:black;stroke-width:1;'/>\n";
				body+="                <ellipse onmousedown='setType(\"ellipse\")' cx='78' cy='17' rx='10' ry='7' style='fill:yellow;stroke:black;stroke-width:1;'/>\n";
				body+="            </svg>\n";
				body+="        </div>\n";
				body+="        <div id='separator' style='width:100%; height:2px; float:top; background-color:#000000;'></div>\n";
				body+="        <div class='width_bar' style='width:100%; height:105px; float:top;'>\n";
				body+="            <div id='separator' style='width:100%; height:5px; float:top; '></div>\n";
				body+="            <div onmousedown='return false;'><center><font size=2><b>WIDTH</b></font></center></div>\n";
				body+="            <svg id='width_board' xmlns='http://www.w3.org/2000/svg' version='1.1' width='100%' height='100%'>\n";
				body+="                <rect id='width0Field' onmousedown='setWidth(0)' class='widthField' x='5' y='5' width='25' height='25'/>\n";
				body+="                <rect id='width2Field' onmousedown='setWidth(2)' class='widthField' x='35' y='5' width='25' height='25'/>\n";
				body+="                <rect id='width4Field' onmousedown='setWidth(4)' class='widthField' x='65' y='5' width='25' height='25'/>\n";
				body+="                <rect id='width7Field' onmousedown='setWidth(7)' class='widthField' x='5' y='35' width='40' height='40'/>\n";
				body+="                <rect id='width12Field' onmousedown='setWidth(12)' class='widthField' x='50' y='35' width='40' height='40' style='fill:rgb(200,200,200);stroke:black;stroke-width:1;'/>\n";
				body+="                <g fill='none' stroke='black'><path onmousedown='setWidth(2)' stroke-linecap='round' d='M40 18 55 18' stroke-width='2'/></g>\n";
				body+="                <g fill='none' stroke='black'><path onmousedown='setWidth(4)' stroke-linecap='round' d='M70 18 85 18' stroke-width='4'/></g>\n";
				body+="                <g fill='none' stroke='black'><path onmousedown='setWidth(7)' stroke-linecap='round' d='M15 55 35 55' stroke-width='7'/></g>\n";
				body+="                <g fill='none' stroke='black'><path onmousedown='setWidth(12)' stroke-linecap='round' d='M60 55 80 55' stroke-width='12'/></g>\n";
				body+="            </svg>\n";
				body+="        </div>\n";
				body+="        <div id='separator' style='width:100%; height:2px; float:top; background-color:#000000;'></div>\n";
				body+="        <div class='main_color_bar' style='width:100%; height:105px; float:top;'>\n";
				body+="            <div id='separator' style='width:100%; height:5px; float:top; '></div>\n";
				body+="            <div onmousedown='return false;'><center><font size=2><b>COLOR</b></font></center></div>\n";
				body+="            <svg id='color_board' xmlns='http://www.w3.org/2000/svg' version='1.1' width='100%' height='100%'>\n";
				body+="                <rect id='secondColorField' x='40' y='35' width='40' height='40' style='fill:rgb(255,255,255);stroke:black;stroke-width:1;'/>\n";
				body+="                <rect id='firstColorField' x='20' y='15' width='40' height='40' style='fill:rgb(0,0,0);stroke:black;stroke-width:1;'/>\n";
				body+="            </svg>\n";
				body+="        </div>\n";
				body+="        <div id='separator' style='width:100%; height:1px; float:top; background-color:#888888;'></div>\n";
				body+="        <div class='color_bar'>\n";
				body+="            <button style='background:#000000' class='color_button' id='default_color_button' />\n";
				body+="            <button style='background:#808080' class='color_button'/>\n";
				body+="            <button style='background:#C0C0C0' class='color_button'/>\n";
				body+="            <button style='background:#FFFFFF' class='color_button'/>\n";
				body+="            <button style='background:#800000' class='color_button'/>\n";
				body+="            <button style='background:#800080' class='color_button'/>\n";
				body+="            <button style='background:#FF0000' class='color_button'/>\n";
				body+="            <button style='background:#FF00FF' class='color_button'/>\n";
				body+="            <button style='background:#008000' class='color_button'/>\n";
				body+="            <button style='background:#808000' class='color_button'/>\n";
				body+="            <button style='background:#00FF00' class='color_button'/>\n";
				body+="            <button style='background:#FFFF00' class='color_button'/>\n";
				body+="            <button style='background:#000080' class='color_button'/>\n";
				body+="            <button style='background:#008080' class='color_button'/>\n";
				body+="            <button style='background:#0000FF' class='color_button'/>\n";
				body+="            <button style='background:#00FFFF' class='color_button'/>\n";
				body+="        </div>\n";
				body+="        <div id='separator' style='width:100%; height:1px; float:top; '></div>\n";
				body+="        <div id='separator' style='width:100%; height:2px; float:top; background-color:#000000;'></div>\n";
				body+="    </div>\n";
				body+="</div>\n";
				body+="<div id='board' style='width:100px; height: 100%; float:left; background-color:#DDDDDD;'>\n";
				body+="<svg id='svg_board' xmlns='http://www.w3.org/2000/svg' version='1.1' width='100%' height='100%'/>\n";
				body+="</div>\n";
				body+="<script>startup('"+ID+"');</script>";
				body+="</BODY>\n";
				return "<HTML>\n"+head+body+"</HTML>\n";
			}
			function PaintReceiver(ID, name){
				this._ID = ID;
				this._name = name;
				this._recWindow = null;
				
				// Zamyka okno prezentacji.
				this.close = function(){
					if (this._recWindow == null) return;
					this._recWindow.close();
					this._recWindow = null;
				}

				// Otwiera nowe okno prezentacji. Generuje jego kod na podstawie zapisanych danych
				this.open = function(){
					if (this._recWindow != null){
						this._recWindow.blur();
						setTimeout((function(){this._recWindow.focus();}).bind(this), 0);
						return;
					}
					this._recWindow = window.open('about:blank','','width=700,height=500');
					this._recWindow.document.write(generateReceiverBody(this._name));
					this._recWindow.onbeforeunload = (this.close).bind(this);
					this._recWindow.blur();
					this._recWindow.focus();
				}
				
				// **********************************************************

				// Repozytorium konstruktorów obiektów graficznych
				this.content = [];
				this.graphObj = {
					path : function(ID, points, color, width){
						var path = document.createElementNS('http://www.w3.org/2000/svg',"polyline");
						path.setAttribute("class", ID);
						path.setAttribute("points", points);
						path.setAttribute("style", "fill:none;stroke:"+color+";stroke-width:"+width);
						path.pointsList = []; path.color = color; path.width = width;
						path.addPoint = function(x,y){
							this.pointsList.push([x,y]);
							this.setAttribute("points", this.getAttribute("points") + x+","+y+" "); 
						};
						return path;
					},
					rect : function(ID, x, y, w, h, color, stroke_color, stroke_width){
						var rect = document.createElementNS('http://www.w3.org/2000/svg',"rect");
						rect.sx=x; rect.sy=y; rect.color = color; 
						rect.stroke_color = stroke_color; rect.stroke_width = stroke_width;
						rect.setAttribute("class", ID);
						rect.setAttribute("style", "fill:"+color+";stroke:"+stroke_color+";stroke-width:"+stroke_width);
						rect.setAttribute("width", w); rect.setAttribute("height", h);
						rect.setAttribute("x", x); rect.setAttribute("y", y);
						rect.setCoords = function(x,y){
							var new_x = this.sx, w = x - this.sx; if (w<0){ w = -w; new_x = x; }
							var new_y = this.sy, h = y - this.sy; if (h<0){ h = -h; new_y = y; }
							this.setAttribute("x", new_x);
							this.setAttribute("y", new_y);
							this.setAttribute("width", w);
							this.setAttribute("height", h);
						}
						return rect;
					},
					ellipse : function(ID, cx, cy, rx, ry, color, stroke_color, stroke_width){
						var ellipse = document.createElementNS('http://www.w3.org/2000/svg',"ellipse");
						ellipse.sx=cx; ellipse.sy=cy; ellipse.color = color; 
						ellipse.stroke_color = stroke_color; ellipse.stroke_width = stroke_width;
						ellipse.setAttribute("class", ID);
						ellipse.setAttribute("style", "fill:"+color+";stroke:"+stroke_color+";stroke-width:"+stroke_width);
						ellipse.setAttribute("cx", cx); ellipse.setAttribute("cy", cy);
						ellipse.setAttribute("rx", rx); ellipse.setAttribute("ry", ry);
						ellipse.setCoords = function(x,y){
							var new_rx = this.sx - x; if (new_rx<0) new_rx = -new_rx;
							var new_ry = this.sy - y; if (new_ry<0) new_ry = -new_ry;
							this.setAttribute("rx", new_rx/2);
							this.setAttribute("ry", new_ry/2);
							this.setAttribute("cx", (this.sx+x)/2);
							this.setAttribute("cy", (this.sy+y)/2);
							var new_cx = this.sx, cx = cx - this.sx; if (cx<0){ cx = -cx; new_cx = cx; }
							var new_cy = this.sy, cy = cy - this.sy; if (cy<0){ cy = -cy; new_cy = cy; }
						}
						return ellipse;
					},
					
				}

				// Funkcje użytkowe
				this.path = function(params){
					var points = ""; for(var i=0; i<params.points.length; ++i) points+=params.points[i][0]+","+params.points[i][1]+" ";
					var p = this.graphObj.path(params.ID, points, params.color, params.width);
					this.content.push(p);
					if (this._recWindow == null) this.open(); else this._recWindow.pushObject(p);					
				}
				
				this.rect = function(params){
					var p = this.graphObj.rect(params.ID, params.x, params.y, params.width, params.height, params.color, params.stroke_color, params.stroke_width);
					this.content.push(p);
					if (this._recWindow == null) this.open(); else this._recWindow.pushObject(p);					
				}
				
				this.ellipse = function(params){
					var p = this.graphObj.ellipse(params.ID, params.cx, params.cy, params.rx, params.ry, params.color, params.stroke_color, params.stroke_width);
					this.content.push(p);
					if (this._recWindow == null) this.open(); else this._recWindow.pushObject(p);					
				}
				
				this.clear = function(){
					this.content = [];
					if (this._recWindow) this._recWindow.clear();
				}
				
				this.remove = function(params){
					ID = params.ID;
					for(var i=0; i<this.content.length; ++i) 
						if (this.content[i].getAttribute("class") == ID)
							this.content.splice(i,1);
					if (this._recWindow) this._recWindow.removeObject(ID);
				}
				
			}
			return new PaintReceiver(ID, name);
		},

		// Funkcja tworząca nadajnik wysyłający wiadomość przez internet
		_createNetworkSender: function(ID){
			function NetworkReceiver(ID){
				this.close = function(){}
				this.open = function(){}
				this._ID = ID;

				// **********************************************************
				// Funkcje użytkowe

				this.path = function(params){
					var meta_msg = "LEONERDO_META:"+JSON.stringify({fname: "PATH", fparams: params, recipient:this._ID});
					subscribeHandshake(this._ID, (function(){sendMessageToUser(this._ID, meta_msg);}).bind(this));
					initHandshake(this._ID);
				}

				this.rect = function(params){
					var meta_msg = "LEONERDO_META:"+JSON.stringify({fname: "RECT", fparams: params, recipient:this._ID});
					subscribeHandshake(this._ID, (function(){sendMessageToUser(this._ID, meta_msg);}).bind(this));
					initHandshake(this._ID);
				}				
				
				this.ellipse = function(params){
					var meta_msg = "LEONERDO_META:"+JSON.stringify({fname: "ELLIPSE", fparams: params, recipient:this._ID});
					subscribeHandshake(this._ID, (function(){sendMessageToUser(this._ID, meta_msg);}).bind(this));
					initHandshake(this._ID);
				}				
				
				this.clear = function(){
					var meta_msg = "LEONERDO_META:"+JSON.stringify({fname: "CLEAR", fparams: {}, recipient:this._ID});
					subscribeHandshake(this._ID, (function(){sendMessageToUser(this._ID, meta_msg);}).bind(this));
					initHandshake(this._ID);
				}
				
				this.remove = function(params){
					var meta_msg = "LEONERDO_META:"+JSON.stringify({fname: "REMOVE", fparams: params, recipient:this._ID});
					subscribeHandshake(this._ID, (function(){sendMessageToUser(this._ID, meta_msg);}).bind(this));
					initHandshake(this._ID);
				}
				
			}
			return new NetworkReceiver(ID);
		},

	
	/*** PUBLIC ***/

		// Zamyka wszystkie obiekty Leonerdo
		closeAllObjects: function(){
			for (obj in this._objects)
				this._objects[obj].close();
		},

		// Tworzy domyślną parę obiektów wysyłający-rysujący i linkuje je ze sobą
		createObject: function(ID, name){
			if (!this._objects[ID]){
				this._objects[ID] = this._createPaintReceiver(ID, name);		// Tworzony jest obiekt frontowy
				this._objects["sender_"+ID] = this._createNetworkSender(ID);	// Tworzony jest obiekt wysyłający na serwer
				this.registerIntraObjectConnection(ID, "sender_"+ID);
			}
			return this._objects[ID];
		},		

		// EDIT: tutaj ustalam co się stanie po naciśnięciu przycisku w oknie konwersacji
		openObject: function(ID, name){
			this.createObject(ID, name);
			this._objects[ID].open();
			return this._objects[ID];
		},
		
		// Zwraca listę obiektów Leonerdo powiązanych z obiektem o podanym ID
		getIntraObjects: function(ID){
			var result = [];
			if (this._intraObjects[ID])
				for (var i=0; i<this._intraObjects[ID].length; ++i){
					var dest_ID = this._intraObjects[ID][i];
					result.push(this._objects[dest_ID]);
				}
			return result;
		},

		// Rejestruje jednostronne powiązanie pomiędzy obiektami: src_ID --> dest_ID
		registerIntraObjectConnection: function(src_ID, dest_ID){
			if ((!this._objects[src_ID]) || (!this._objects[dest_ID])) return;
			if (!this._intraObjects[src_ID]) this._intraObjects[src_ID]=[];
			this._intraObjects[src_ID].push(dest_ID);
		},

		// Obsługuje nową wiadomość, która przyszła z serwera	
		receiveMessage: function(authorID, command, authorName){
			if (authorID != command.recipient) try {
				var obj = this.openObject(authorID, authorName);
				if (command.fname == "PATH") obj.path(command.fparams);
				if (command.fname == "RECT") obj.rect(command.fparams);
				if (command.fname == "ELLIPSE") obj.ellipse(command.fparams);
				if (command.fname == "CLEAR") obj.clear();
				if (command.fname == "REMOVE") obj.remove(command.fparams);
			} catch (err){
				console.log("receiveMessage error: " + err);
			}
		},
		
		
		
		// TEST!
		testCreate2Paints: function(){
			this._objects["TEST1"] = this._createPaintReceiver("TEST1");
			this._objects["TEST2"] = this._createPaintReceiver("TEST2");
			this.registerIntraObjectConnection("TEST1", "TEST2");
			this.registerIntraObjectConnection("TEST2", "TEST1");
			this._objects["TEST1"].open();
			this._objects["TEST2"].open();
		},
		
		// TEST 2
		test2: function(){
			this._objects["TEST1"] = this._createPaintReceiver("TEST1");
			this._objects["TEST1"].open();
		},
		
}

addScript("var Leonerdo = " + obj2str(Leonerdo));
addScript("this.onbeforeunload = function (){Leonerdo.closeAllObjects();}");


// TEST!
//addScript("if (__d) Leonerdo.testCreate2Paints();");
//addScript("if (__d) Leonerdo.test2();");
